﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using System.Net;

namespace Asol.Reporting.Support
{
    /// <summary>
    /// Asistent běhu, obsahuje sady podpůrných funkcí, knihovny, pracovní data atd.
    /// </summary>
    public partial class Assistant
    {
        #region Singleton, protected (s instančními daty pracují jen public statické metody této třídy)
        /// <summary>
        /// Živá instance asistenta
        /// </summary>
        protected static Assistant Live
        {
            get
            {
                if (_Live == null)
                {
                    lock (_Lock)
                    {
                        if (_Live == null)
                            _Live = new Assistant();
                    }
                }
                return _Live;
            }
        }
        private Assistant()
        {
            InitializeMethodAttribute.CallInitializeMethods(this);
        }
        private static object _Lock = new object();
        private static Assistant _Live = null;
        #endregion
        #region Umístění aplikace, adresáře
        /// <summary>
        /// Vrátí plné jméno souboru v dané cestě.
        /// Je zadán druh cesty a holé, nebo relativní, nebo plné jméno souboru.
        /// Pokud je soubor zadán s relativní cestou, bude zkombinován do dané cesty (PathType).
        /// Pokud je soubor zadán s úplnou cestou, bude vráceno zadané jméno souboru, nebude tedy přemístěn do dané cesty (PathType).
        /// </summary>
        /// <param name="pathType"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static string GetFileNameOnPath(PathType pathType, string fileName)
        {
            if (String.IsNullOrEmpty(fileName)) return fileName;
            if (System.IO.Path.IsPathRooted(fileName)) return fileName;
            string path = GetPath(pathType);
            return System.IO.Path.Combine(path, fileName);
        }
        /// <summary>
        /// Vrátí název adresáře daného typu. Zajistí jeho existenci.
        /// </summary>
        /// <param name="pathType"></param>
        /// <returns></returns>
        public static string GetPath(PathType pathType)
        {
            return GetPath(pathType, null);
        }
        /// <summary>
        /// Vrátí název adresáře daného typu + daný podadresář. Zajistí jeho existenci.
        /// </summary>
        /// <param name="pathType"></param>
        /// <returns></returns>
        public static string GetPath(PathType pathType, string subDir)
        {
            string path = null;
            switch (pathType)
            {
                case PathType.None:
                    path = "";
                    break;
                case PathType.ExecutablePath:
                    path = Live._AppPath;
                    break;
                case PathType.UserApplicationData:
                    path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create);
                    path = System.IO.Path.Combine(path, PATH_APPLICATION);
                    break;
                case PathType.CommonApplicationData:
                    path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData, Environment.SpecialFolderOption.Create);
                    path = System.IO.Path.Combine(path, PATH_APPLICATION);
                    break;
                case PathType.ApplicationCache:
                    path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData, Environment.SpecialFolderOption.Create);
                    path = System.IO.Path.Combine(path, PATH_APPLICATION, PATH_CACHE);
                    break;
                case PathType.TemporaryPath:
                    path = System.IO.Path.GetTempPath();
                    path = System.IO.Path.Combine(path, PATH_APPLICATION);
                    break;
            }
            if (!String.IsNullOrEmpty(path))
            {
                if (!String.IsNullOrEmpty(subDir))
                    path = System.IO.Path.Combine(path, subDir);
                if (!System.IO.Directory.Exists(path))
                    System.IO.Directory.CreateDirectory(path);
            }
            return path;
        }
        protected const string PATH_APPLICATION = "AsolReporting";
        protected const string PATH_CACHE = "Cache";
        /// <summary>
        /// Druh adresáře
        /// </summary>
        public enum PathType
        {
            /// <summary>Žádný adresář</summary>
            None = 0,
            /// <summary>Adresář aplikace (možná bez práv zápisu)</summary>
            ExecutablePath,
            /// <summary>Adresář dat aplikace (s právy zápisu), společný s ostatními uživateli</summary>
            CommonApplicationData,
            /// <summary>Adresář dat aplikace (s právy zápisu), pouze pro aktuálního uživatele</summary>
            UserApplicationData,
            /// <summary>Adresář dat aplikace (s právy zápisu), společný s ostatními uživateli, pracovní podadresář /Cache </summary>
            ApplicationCache,
            /// <summary>Temporary adresář</summary>
            TemporaryPath
        }

        /// <summary>
        /// Inicializace adresářů a cest
        /// </summary>
        [InitializeMethod(-32767)]           // Načítání adresářů nepoužívá nic předchozího, měla by to být první inicializační metoda
		protected void InitPaths()
        {
            this._AppPath = System.IO.Path.GetDirectoryName(this.GetType().Assembly.Location);
        }
        private string _AppPath;
    	#endregion
        #region Run GUI, Main window, inner forms
        /// <summary>
        /// Vytvoří a vrátí IDisposable obálku aplikace
        /// </summary>
        /// <param name="withVisualStyles"></param>
        /// <returns></returns>
        public static AppWrapper CreateApplication(bool withVisualStyles)
        {
            if (withVisualStyles)
                Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            AppWrapper app = new AppWrapper();
            return app;
        }
        /// <summary>
        /// Spustí GUI aplikace (main okno).
        /// </summary>
        /// <param name="mainFormType"></param>
        public static void RunGui(Type mainFormType)
        {
            Live._RunGui(mainFormType, null);
        }
        /// <summary>
        /// Spustí GUI aplikace (main okno), ale před tím spustí danou akci a předá jí připravený Form.
        /// </summary>
        /// <param name="mainFormType"></param>
        /// <param name="beforeRun"></param>
        public static void RunGui(Type mainFormType, Action<Form> beforeRun)
        {
            Live._RunGui(mainFormType, beforeRun);
        }
        private void _RunGui(Type mainFormType, Action<Form> beforeRun)
        {
            try
            {
                object mainForm = System.Activator.CreateInstance(mainFormType);
                if (!(mainForm is Form))
                    return;
                Form form = mainForm as Form;
                form.ShowInTaskbar = true;
                ApplicationContext appc = new ApplicationContext(form);
                _GuiThread = System.Threading.Thread.CurrentThread;
                _GuiThread.Name = "Main GUI Thread";
                _MainForm = form;
                if (beforeRun != null)
                    beforeRun(form);
                Application.Run(appc);           // Tady stojí toto vlákno až do skončení aplikace. V případě vizuální akce ji zdejší vlákno obsluhuje.
            }
            catch (Exception exc)
            {
                ShowError(exc, "Chyba při běhu aplikace. %0", exc.Message);
            }
            finally
            {
                _AppExit();
                _MainForm = null;
                _GuiThread = null;
            }
        }
        /// <summary>
        /// Zobrazí daný Form v threadu MainFormu (=GUI thread), a toto okno (childForm) zobrazí s parametrem owner = MainForm.
        /// </summary>
        /// <param name="childForm"></param>
        public static void ShowFormInGuiThread(Form childForm)
        {
            System.Windows.Forms.Form mainForm = MainForm;
            if (mainForm == null || !mainForm.IsMdiContainer) return;

            if (mainForm.InvokeRequired)
                mainForm.BeginInvoke(new Action<Form, Form>(_ShowFormInGuiThread), mainForm, childForm);
            else
                _ShowFormInGuiThread(mainForm, childForm);
        }
        private static void _ShowFormInGuiThread(Form mainForm, Form childForm)
        {
            childForm.ShowInTaskbar = false;
            childForm.Show(mainForm);
        }

        /// <summary>
        /// Povinná metoda. Je třeba ji zavolat před koncem aplikace.
        /// </summary>
        public static void AppDone()
        {
            Live._AppExit();
            // Live._FunctionMenuDispose();
            // Live._ServicesUnLoad();
            // Live._TraceClose();
        }
        private void _AppExit()
        {
            if (this._ExitProcesed) return;

            if (this.AppExiting != null)
                this.AppExiting(this, EventArgs.Empty);

            this._ExitProcesed = true;
        }
        /// <summary>
        /// Přidá požadovanou akci do seznamu událostí volaných na konci života aplikace.
        /// Typicky se používá k ukládání konfigurací.
        /// </summary>
        /// <param name="action"></param>
        public static void CallMeOnAppExit(System.EventHandler action)
        {
            Live.AppExiting += action;
        }
        /// <summary>Main form of application</summary>
        public static Form MainForm { get { return Live._MainForm; } }
        /// <summary>Thread GUI</summary>
        public static Thread GuiThread { get { return Live._GuiThread; } }
        private Form _MainForm;
        private Thread _GuiThread;
        private event EventHandler AppExiting;
        private bool _ExitProcesed = false;
        #endregion
        #region Run external process
        /// <summary>
        /// Spustí externí proces v operačním systému, synchronně = čeká na jeho exit.
        /// </summary>
        /// <param name="processFile"></param>
        /// <param name="arguments"></param>
        public static int RunProcessSync(string processFile, string arguments)
        {
            int existCode = -1;
            bool useWaitCursor = System.Windows.Forms.Application.UseWaitCursor;
            try
            {
                using (Process process = new Process())
                {
                    process.StartInfo.FileName = processFile;
                    process.StartInfo.Arguments = arguments;
                    System.Windows.Forms.Application.UseWaitCursor = true;
                    process.Start();
                    process.WaitForExit();
                    existCode = process.ExitCode;
                }
            }
            finally
            {
                System.Windows.Forms.Application.UseWaitCursor = useWaitCursor;
            }
            return existCode;
        }
        #endregion
        #region Run method with catch
        /// <summary>
        /// Spustí danou akci v try-catch, případné chyby ohlásí a vrátí řízení.
        /// Delegát na anonymní akci se píše: Assistant.Run(delegate { akce(parametry); }); !!!
        /// </summary>
        /// <param name="method"></param>
        public static void Run(Action method)
        {
            try
            {
                method();
            }
            catch (Exception exc)
            {
                ShowError(exc, "Došlo k chybě");
            }
        }
        #endregion
        #region Parametry aplikace z příkazové řádky
        /// <summary>
        /// Detekce parametrů aplikace. Parametry jsou uloženy v Steward.ParamsList, ke čtení slouží metoda Steward.ParamGet().
        /// Parametry:
        /// N=UserName
        /// P=Password
        /// D=DataProfile name
        /// C=ConfigFile
        /// Sd=ConnectString (čitelný)
        /// Sc=ConnectString (Base64)
        /// Systém detekuje všechny vstupní parametry, zadané ve formě Text=Hodnota
        /// </summary>
        /// <param name="arguments">Soupis předaných parametrů</param>
        public static void ParameterStore(string[] arguments)
        {
            Live._ParamStore(arguments);
        }
        /// <summary>
        /// Najde a vrátí parametr, podle jeho daného klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="key">Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static string ParamGet(string key, bool ignoreCase)
        {
            return Live._ParamGet(new string[] { key }, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí parametr, podle jeho daného klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="keyList">Hledané parametry, vrací se první z nalezených. Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static string ParamGet(string[] keyList, bool ignoreCase)
        {
            return Live._ParamGet(keyList, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí parametry, které vyhovují danému klíči (daným klíčům), vrací je včetně jejich klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="key">Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static List<NameValueItem> ParamsGet(string key, bool ignoreCase)
        {
            return Live._ParamsGet(new string[] { key }, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí parametry, které vyhovují danému klíči (daným klíčům), vrací je včetně jejich klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="keyList">Hledané parametry, vrací se první z nalezených. Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static List<NameValueItem> ParamsGet(string[] keyList, bool ignoreCase)
        {
            return Live._ParamsGet(keyList, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí počet parametrů, které odpovídají danému klíči / klíčům.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí 0.
        /// </summary>
        /// <param name="key">Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static int ParamCount(string key, bool ignoreCase)
        {
            return Live._ParamCount(new string[] { key }, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí počet parametrů, které odpovídají danému klíči / klíčům.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí 0.
        /// </summary>
        /// <param name="keyList">Hledané parametry, vrací se první z nalezených. Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static int ParamCount(string[] keyList, bool ignoreCase)
        {
            return Live._ParamCount(keyList, ignoreCase);
        }
        /// <summary>
        /// Parametry spuštění.
        /// Key je hodnota před rovnítkem, Value je hodnota za rovnítkem.
        /// Pokud argument neobsahuje rovnítko, pak je Key prázdné.
        /// Nejde o Dictionary, jde tedy možno mít více argumentů se shodným Key (nebo bez Key).
        /// </summary>
        public static List<NameValueItem> ParamList { get { return new List<NameValueItem>(Live._ParamList); } }
        #region Parametry - privátní instanční vrstva
		/// <summary>
		/// Uloží do sebe parametry předané při spouštění aplikace
		/// </summary>
		/// <param name="arguments"></param>
		private void _ParamStore(string[] arguments)
		{
            this._ParamList = new List<NameValueItem>();
			if (arguments == null) return;

			foreach (string argument in arguments)
			{
				string text = argument.Trim();
				int posE = text.IndexOf("=");              // Kde je rovnítko
				int posU = text.IndexOf("\"");             // Kde je uvozovka
				if (posE >= 0 && posU >= 0 && posU < posE) // Pokud je tam obojí, ale uvozovka je dřív než rovnítko, pak jako by rovnítko nebylo
					posE = -1;
				string name = String.Empty;
				string value = text;
                if (posE < 0)
                {
                    name = value;
                    value = null;
                }
                else if (posE == 0)
                {
                    value = text.Substring(1);
                }
                else if (posE > 0)
                {
                    name = text.Substring(0, posE).Trim();
                    value = text.Substring(posE + 1).Trim();
                }
                this._ParamList.Add(new NameValueItem(name, value));
			}
		}
		/// <summary>
		/// Metoda najde a vrátí první parametr spuštění, který odpovídá danému klíči.
		/// Pokud nenajde nic, vrací String.Empty
		/// </summary>
		/// <param name="key"></param>
		/// <param name="ignoreCase"></param>
		/// <returns></returns>
		private string _ParamGet(string key, bool ignoreCase)
		{
			return this._ParamGet(new string[] { key }, ignoreCase);
		}
		/// <summary>
		/// Metoda najde a vrátí první parametr spuštění, který odpovídá danému klíči.
		/// Pokud nenajde nic, vrací null
		/// </summary>
		/// <param name="keyList"></param>
		/// <param name="ignoreCase"></param>
		/// <returns></returns>
		private string _ParamGet(string[] keyList, bool ignoreCase)
		{
            List<NameValueItem> values = this._ParamsGet(keyList, ignoreCase);
			if (values.Count == 0) return null;
			return values[0].Value;
		}
		/// <summary>
		/// Metoda určí počet parametrů s daným klíčem.
		/// Lze využít pro detekci existence určitého parametru.
		/// </summary>
		/// <param name="keyList"></param>
		/// <param name="ignoreCase"></param>
		/// <returns>Počet odpovídajících parametrů</returns>
		private int _ParamCount(string[] keyList, bool ignoreCase)
		{
            List<NameValueItem> values = this._ParamsGet(keyList, ignoreCase);
			return values.Count;
		}
		/// <summary>
		/// Metoda najde a vrátí všechny parametry spuštění, které odpovídají danému klíči.
		/// </summary>
		/// <param name="keyList"></param>
		/// <param name="ignoreCase"></param>
		/// <returns></returns>
		private List<NameValueItem> _ParamsGet(string[] keyList, bool ignoreCase)
		{
			List<NameValueItem> values = new List<NameValueItem>();
			foreach (NameValueItem param in this._ParamList)
			{
				bool equal = false;
				if (keyList == null || keyList.Length == 0)
					equal = true;
				else
				{
					foreach (string key in keyList)
					{
						bool equalOne = (ignoreCase ? String.Equals(param.Name, key, StringComparison.OrdinalIgnoreCase) : String.Equals(param.Name, key, StringComparison.Ordinal));
						if (equalOne)
						{
							equal = true;
							break;
						}
					}
				}
				if (equal)
					values.Add(param);
			}
			return values;
		}
        /// <summary>
		/// Parametry spuštění.
		/// Key je hodnota před rovnítkem, Value je hodnota za rovnítkem.
		/// Pokud argument neobsahuje rovnítko, pak je Key prázdné.
		/// Nejde o Dictionary, jde tedy možno mít více argumentů se shodným Key (nebo bez Key).
		/// </summary>
        private List<NameValueItem> _ParamList;
        #endregion
        #endregion
        #region Zdroj dat (IDataSource), implicitní formátovací styl
        /// <summary>
        /// Datový zdroj. Implicitně obsahuje nejvýhodnější datový zdroj, nebo zdroj definovaný parametrem procesu: Source="Namespace.Name"
        /// Zdroj je možno kdykoliv i explicitně vložit (Assistant.DataSource = instance)
        /// </summary>
        public static IDataSource DataSource
        { 
            get { return Live._DataSource; }
            set { Live._DataSource = value; }
        }
        /// <summary>
        /// Autoinicializační property datového zdroje
        /// </summary>
        private IDataSource _DataSource
        {
            get
            {
                if (this.__DataSource == null)
                    this.__DataSource = this._CreateDataSource();
                return this.__DataSource;
            }
            set
            {
                if (value != null)
                    value.Initialize();
                this.__DataSource = value;
            }
        }
        /// <summary>
        /// Uložená instance datového zdroje
        /// </summary>
        private IDataSource __DataSource;
        /// <summary>
        /// Vyhledá a instancuje patřičný datový zdroj
        /// </summary>
        /// <returns></returns>
        private IDataSource _CreateDataSource()
        {
            List<IDataSource> sources = this._GetServiceObjectList<IDataSource>(true).ToList();
            IDataSource iSource = this._SelectDataSource(sources);
            iSource.Initialize();
            return iSource;
        }
        /// <summary>
        /// Z dodaného soupisu pluginů typu IDataSource vybere požadovaný (parametrem Source=Namespace.Name), 
        /// anebo zvolí ten nejlepší podle priority. Pokud neexistuje požadovaný nebo žádný, ohlásí chybu.
        /// </summary>
        /// <param name="sources"></param>
        /// <returns></returns>
        private IDataSource _SelectDataSource(List<IDataSource> sources)
        {
            if (sources.Count == 0)
                throw new DevException("Pro tvorbu reportů nebyl nalezen žádný datový zdroj (kód typu IDataSource).");

            IDataSource iSource = null;
            string name = this._ParamGet("Source", true);
            if (!String.IsNullOrEmpty(name))
            {   // Najít explicitně vyžádaný zdroj dat:
                iSource = sources.FirstOrDefault(s => s.GetType().NsName().EndsWith(name));
                if (iSource == null)
                    throw new DevException("Pro tvorbu reportů nebyl nalezen požadovaný datový zdroj %0.", name);
            }
            else
            {   // Najít první zdroj s nejvyšší prioritou:
                if (sources.Count > 1)
                    sources.Sort((a,b) =>  b.Priority.CompareTo(a.Priority));
                iSource = sources[0];
            }

            // Vracím new objekt daného konkrétního typu, typovaný na interface:
            return ObjectGenerator<IDataSource>(iSource.GetType());
        }
        /// <summary>
        /// Obsahuje výchozí formátovací styl, který formátuje standardní systémové typy.
        /// </summary>
        public static FormatStyle DefaultFormatStyle
        {
            get
            {
                if (Live._DefaultFormatStyle == null)
                    Live._DefaultFormatStyle = new FormatStyle();
                return Live._DefaultFormatStyle;
            }
        }
        private FormatStyle _DefaultFormatStyle;
        #endregion
        #region Systémové dialogy, zjednodušené adaptéry na ně
        /// <summary>
        /// Uživatel může vybrat soubor v dialogu OpenFile.
        /// Metoda vrací null = nevybráno / string = jméno souboru.
        /// </summary>
        /// <param name="title">Titulek dialogového okna</param>
        /// <param name="filter">String popisující filtr</param>
        /// <param name="initialFile">Výchozí adresář</param>
        /// <returns>Vybraný soubor</returns>
        public static string GetOpenFileResult(string title, string filter, string initialFile)
        {
            return GetOpenFileResult(title, filter, 0, initialFile);
        }
        /// <summary>
        /// Uživatel může vybrat soubor v dialogu OpenFile.
        /// Metoda vrací null = nevybráno / string = jméno souboru.
        /// </summary>
        /// <param name="title">Titulek dialogového okna</param>
        /// <param name="filter">String popisující filtr</param>
        /// <param name="filterIndex">Index aktuálně vybraného filtru, base 0</param>
        /// <param name="initialFile">Výchozí adresář</param>
        /// <returns>Vybraný soubor</returns>
        public static string GetOpenFileResult(string title, string filter, int filterIndex, string initialFile)
        {
            string initialDir = (String.IsNullOrEmpty(initialFile) ? "" : System.IO.Path.GetDirectoryName(initialFile));
            using (OpenFileDialog ofd = GetOpenFileDialog(title, filter, filterIndex, initialDir, false))
            {
                ofd.FileName = (String.IsNullOrEmpty(initialFile) ? "" : System.IO.Path.GetFileName(initialFile));
                var result = ofd.ShowDialog();
                if (result == DialogResult.OK) return ofd.FileName;
            }
            return null;
        }
        /// <summary>
        /// Vrátí okno pro dialog OpenFile.
        /// Okno není otevřené, lze jej donastavit a otevřít.
        /// </summary>
        /// <param name="title">Titulek dialogového okna</param>
        /// <param name="filter">String popisující filtr</param>
        /// <param name="initialDir">Výchozí adresář</param>
        /// <param name="multiSelect">Možnost multiselectu</param>
        /// <returns>Dialogové okno třídy OpenFileDialog</returns>
        public static OpenFileDialog GetOpenFileDialog(string title, string filter, string initialDir, bool multiSelect)
        {
            return GetOpenFileDialog(title, filter, 0, initialDir, multiSelect);
        }
        /// <summary>
        /// Vrátí okno pro dialog OpenFile.
        /// Okno není otevřené, lze jej donastavit a otevřít.
        /// </summary>
        /// <param name="title">Titulek dialogového okna</param>
        /// <param name="filter">String popisující filtr</param>
        /// <param name="filterIndex">Index aktuálně vybraného filtru, base 0</param>
        /// <param name="initialDir">Výchozí adresář</param>
        /// <param name="multiSelect">Možnost multiselectu</param>
        /// <returns>Dialogové okno třídy OpenFileDialog</returns>
        public static OpenFileDialog GetOpenFileDialog(string title, string filter, int filterIndex, string initialDir, bool multiSelect)
        {
            OpenFileDialog ofd = new OpenFileDialog();

            ofd.AddExtension = false;
            ofd.AutoUpgradeEnabled = true;
            ofd.DefaultExt = "";
            ofd.DereferenceLinks = true;
            ofd.Filter = filter;
            ofd.FilterIndex = filterIndex;
            ofd.CheckFileExists = true;
            ofd.CheckPathExists = true;
            ofd.Multiselect = multiSelect;
            ofd.InitialDirectory = initialDir;
            ofd.RestoreDirectory = true;
            ofd.SupportMultiDottedExtensions = true;
            ofd.Title = title;
            ofd.ValidateNames = false;

            return ofd;
        }
        /// <summary>
        /// Uživatel může vybrat soubor v dialogu SaveFile.
        /// Metoda vrací null = nevybráno / string = jméno souboru.
        /// </summary>
        /// <param name="title">Titulek dialogového okna</param>
        /// <param name="filter">String popisující filtr</param>
        /// <param name="initialDir">Výchozí adresář</param>
        /// <param name="initialFile"></param>
        /// <param name="addExt"></param>
        /// <returns></returns>
        public static string GetSaveFileResult(string title, string filter, string initialDir, string initialFile, string addExt)
        {
            using (SaveFileDialog sfd = GetSaveFileDialog(title, filter, initialDir, initialFile, addExt))
            {
                var result = sfd.ShowDialog();
                if (result == DialogResult.OK) return sfd.FileName;
            }
            return null;
        }
        /// <summary>
        /// Vrátí okno pro dialog SaveFile.
        /// Okno není otevřené, lze jej donastavit a otevřít.
        /// </summary>
        /// <param name="title">Titulek dialogového okna</param>
        /// <param name="filter">String popisující filtr</param>
        /// <param name="initialDir">Výchozí adresář</param>
        /// <param name="initialFile"></param>
        /// <param name="addExt"></param>
        /// <returns></returns>
        public static SaveFileDialog GetSaveFileDialog(string title, string filter, string initialDir, string initialFile, string addExt)
        {
            SaveFileDialog sfd = new SaveFileDialog();

            sfd.AddExtension = (!String.IsNullOrEmpty(addExt));
            sfd.AutoUpgradeEnabled = true;
            sfd.CreatePrompt = false;
            sfd.DefaultExt = addExt;
            sfd.FileName = initialFile;
            sfd.Filter = filter;
            sfd.FilterIndex = 0;
            sfd.CheckPathExists = true;
            sfd.InitialDirectory = initialDir;
            sfd.OverwritePrompt = true;
            sfd.RestoreDirectory = true;
            sfd.SupportMultiDottedExtensions = true;
            sfd.Title = title;
            sfd.ValidateNames = false;

            return sfd;
        }
        #endregion
        #region Hlášky (chyby, warningy, dialogy)
        /// <summary>
        /// Inicializace systému překládaných textů
        /// </summary>
        [InitializeMethod()]
        protected void InitMessages()
        {
            this._Messages = Messages.FromFile("ReportingMessages.xml");
            this.AppExiting += this._Messages.SaveMessages;
        }
        /// <summary>
        /// Získá zprávu
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string GetMessage(string message, params object[] data)
        {
            return Live._GetMessage(message, data);
        }
        /// <summary>
        /// Zobrazí dialogové okno s chybou
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        public static void ShowError(string message, params object[] data)
        {
            string text = GetMessage(message, data);
            System.Windows.Forms.MessageBox.Show(text, "Reporting", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        /// <summary>
        /// Zobrazí dialogové okno s chybou
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        public static void ShowError(Exception exc, string message, params object[] data)
        {
            string msg, stc;
            ReadException(exc, out msg, out stc);
            string text = GetMessage(message, data); // + Environment.NewLine + msg + Environment.NewLine + stc;
            if (exc is UserException)
                System.Windows.Forms.MessageBox.Show(text, "Reporting", MessageBoxButtons.OK, MessageBoxIcon.Error);
            else if (exc is AdminException)
                System.Windows.Forms.MessageBox.Show(text, "Reporting", MessageBoxButtons.OK, MessageBoxIcon.Error);
            else
                Asol.Reporting.Support.Forms.ErrorForm.Show(text + Environment.NewLine + exc.Message, exc);
        }
        /// <summary>
        /// Z dodané chyby a z jejích InnerException přečte jejich Message a StackTrace, a uloží je do out stringů.
        /// </summary>
        /// <param name="exc"></param>
        /// <param name="msg"></param>
        /// <param name="stc"></param>
        public static void ReadException(Exception exc, out string msg, out string stc)
        {
            msg = "";
            stc = "";
            while (exc != null)
            {
                msg += Environment.NewLine + exc.Message;
                stc += Environment.NewLine + exc.StackTrace;
                exc = exc.InnerException;
            }
        }
        /// <summary>
        /// Zobrazí dialogové okno s varováním
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        public static void ShowWarning(string message, params object[] data)
        {
            string text = GetMessage(message, data);
            System.Windows.Forms.MessageBox.Show(text, "Reporting", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
        private string _GetMessage(string message, params object[] data)
        {
            string text = this._Messages.GetText(message, this._Context.Language);

            if (data != null)
            {
                for (int i = 0; i < data.Length; i++)
                {
                    string s = "%" + i.ToString();            // Varianta A: "Zpráva obsahuje odkazy %0, %1."
                    if (!text.Contains(s))
                        s = "{" + i.ToString() + "}";         // Varianta B: "Zpráva obsahuje odkazy {0}, {1}."
                    if (text.Contains(s))
                    {
                        object dat = data[i];
                        string txt = (dat == null ? "{null}" : dat.ToString());
                        text = text.Replace(s, txt);
                    }
                }
            }

            return text;
        }
        private Messages _Messages;
        #endregion
        #region Context
        /// <summary>
        /// Inicializace kontextu aplikace
        /// </summary>
        [InitializeMethod(-512)]
        protected void InitContext()
        {
            this._IsProgrammer = System.Diagnostics.Debugger.IsAttached;
            this._Context = new Context() { Language = "CZ" };
        }
        /// <summary>
        /// Aktuálně pracuje programátor (tedy alespoň v okamžiku startu aplikace).
        /// </summary>
        public static bool IsProgrammer { get { return Live._IsProgrammer; } } private bool _IsProgrammer;
        /// <summary>
        /// Aktuální kontext aplikace
        /// </summary>
        public static Context Context { get { return Live._Context; } }
        private Context _Context;
        #endregion
        #region DisposableScope (oblast kódu s garantovaným provedením akce na jejím konci, obdoba try - finally)
        /// <summary>
        /// <para>
        /// Metoda vrátí IDisposable objekt, který při svém Dispose zajistí provedení dané akce.
        /// Výstup této metody je tedy nutno použít v using patternu.
        /// Dispose (tedy požadovaná akce) se provede, i když uvnitř bloku dojde k chybě.
        /// </para>
        /// <para>
        /// Použití této metody je následující: using(Assistant.DisposableScope(delegate() { příkaz_na_konci_1(); proměnná = hodnota; ...}) { vnitřní blok příkazů }
        /// Návratovou hodnotu metody DisposableScope() není třeba v using patternu explicitně ukládat, beztak tam nic neuvidíme.
        /// </para>
        /// </summary>
        /// <param name="actionOnDispose">Akce provedená na konci bloku using() { }. Pište ji: (delegate() { příkaz_na_konci_1(); proměnná = hodnota; ...}).</param>
        /// <returns></returns>
        public static IDisposable DisposableScope(Action actionOnDispose)
        {
            return new DisposableContainer(actionOnDispose);
        }
        /// <summary>
        /// Privátní třída, která provede danou akci na konci života (IDisposableDispose() => Action)
        /// </summary>
        private class DisposableContainer : IDisposable
        {
            public DisposableContainer(Action actionOnDispose)
            {
                this._IsDisposed = false;
                this._ActionOnDispose = actionOnDispose;
            }
            private bool _IsDisposed;
            private Action _ActionOnDispose;
            void IDisposable.Dispose()
            {
                if (!this._IsDisposed && this._ActionOnDispose != null)
                    this._ActionOnDispose();
                this._IsDisposed = true;
            }
        }
        #endregion
        #region Malé metody na závěr
        /// <summary>
        /// Vrátí první not null hodnotu z daného soupisu
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="values"></param>
        /// <returns></returns>
        public static T FirstNotNull<T>(params T[] values)
        {
            foreach (T value in values)
            {
                if (value != null) return value;
            }
            return default(T);
        }        
        #endregion
        #region Konvertory textu na hodnotu
        /// <summary>
        /// Pokusí se z daného textu najít a vrátit DateTime
        /// </summary>
        /// <param name="text"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool ParseText(string text, out DateTime value)
        {
            value = DateTime.MinValue;
            if (String.IsNullOrEmpty(text))
                return false;
            if (DateTime.TryParse(text, DateTimeFormat, System.Globalization.DateTimeStyles.AllowWhiteSpaces | System.Globalization.DateTimeStyles.AssumeLocal, out value))
                return true;
            string[] formats = new string[]
            {
                "d.M.yyyy", "d-M-yyyy", "d/M/yyyy",
                "d.M.yyyy H:mm", "d-M-yyyy H:mm", "d/M/yyyy H:mm",
                "d.M.", "d-M", "d/M",
                "d.M. H:mm", "d-M H:mm", "d/M H:mm"
            };
            if (DateTime.TryParseExact(text, formats, DateTimeFormat, System.Globalization.DateTimeStyles.AllowWhiteSpaces | System.Globalization.DateTimeStyles.AssumeLocal, out value))
                return true;
            return false;
        }
        /// <summary>
        /// Pokusí se z daného textu najít a vrátit TimeSpan
        /// </summary>
        /// <param name="text"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool ParseText(string text, out TimeSpan value)
        {
            value = TimeSpan.Zero;
            if (String.IsNullOrEmpty(text))
                return false;
            if (TimeSpan.TryParse(text, DateTimeFormat, out value))
                return true;
            string[] formats = new string[]
            {
                "H:mm", "H:mm:ss", 
                "H-mm", "H-mm-ss", 
                "d,H:mm", "d,H:mm:ss", 
                "d,H-mm", "d,H-mm-ss"
            };
            if (TimeSpan.TryParseExact(text, formats, DateTimeFormat, out value))
                return true;
            return false;
        }
        /// <summary>
        /// Formátování data a času pro aktuální CultureInfo
        /// </summary>
        public static System.Globalization.DateTimeFormatInfo DateTimeFormat
        {
            get
            {
                if (Live._DateTimeFormat == null)
                {
                    Live._DateTimeFormat = (System.Globalization.DateTimeFormatInfo)System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.Clone();
                }
                return Live._DateTimeFormat;
            }
            set
            {
                Live._DateTimeFormat = value;
            }
        }
        private System.Globalization.DateTimeFormatInfo _DateTimeFormat;
        #endregion
    }
    #region class AppWrapper
    /// <summary>
    /// Obálka aplikace
    /// </summary>
    public class AppWrapper : IDisposable
    {
        /// <summary>
        /// Spustí GUI aplikace (main okno).
        /// </summary>
        /// <param name="mainFormType"></param>
        public void RunGui(Type mainFormType)
        {
            Assistant.RunGui(mainFormType);
        }
        /// <summary>
        /// Spustí GUI aplikace (main okno).
        /// </summary>
        /// <param name="mainFormType"></param>
        /// <param name="beforeRun"></param>
        public void RunGui(Type mainFormType, Action<Form> beforeRun)
        {
            Assistant.RunGui(mainFormType, beforeRun);
        }
        void IDisposable.Dispose()
        {
            Assistant.AppDone();
        }
    }
    #endregion
    #region class Context
    /// <summary>
    /// Třída obsahující průběžné informace o kontextu běhu aplikace
    /// </summary>
    public class Context
    {
        /// <summary>
        /// Aktuální jazyk (pro GUI)
        /// </summary>
        public string Language { get; set; }
    }
    #endregion
    #region class NameValueItem : Jeden pár Jméno=Hodnota
    /// <summary>
    /// Jeden pár Jméno=Hodnota
    /// </summary>
    public class NameValueItem
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public NameValueItem(string name, string value)
        {
            this.Name = name;
            this.Value = value;
        }
        /// <summary>Jméno</summary>
        public string Name { get; private set; }
        /// <summary>Hodnota</summary>
        public string Value { get; private set; }
        /// <summary>Máme hodnotu (není null)?</summary>
        public bool HasValue { get { return (this.Value != null); } }
    }
    #endregion
    #region class Ftp : wrapper na FTP server
    public static class Ftp
    {
        /// <summary>
        /// Z daného FTP serveru načte a stáhne daný soubor, jeho obsah vrátí.
        /// Pozor, paměťově náročná metoda (celý obsah souboru jde do paměti serveru).
        /// </summary>
        /// <param name="server"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public static byte[] FtpDownloadContent(string server, string userName, string password, string file)
        {
            byte[] content = null;
            string uri = null;
            try
            {
                using (System.Net.WebClient wc = new System.Net.WebClient())
                {
                    wc.Credentials = _FtpGetCredentials(userName, password);
                    uri = _FtpGetUri(server, file);
                    content = wc.DownloadData(uri);
                }
            }
            catch (Exception exc)
            {
                Assistant.ShowError("Při stahování dat %0 došlo k chybě %1.", uri, exc.Message);
            }
            return content;
        }
        /// <summary>
        /// Z daného FTP serveru načte a stáhne daný soubor, a uloží jej do daného lokálního souboru.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="ftpFile"></param>
        /// <returns></returns>
        public static void FtpDownloadFile(string server, string userName, string password, string ftpFile, string localFile)
        {
            string uri = null;
            try
            {
                using (System.Net.WebClient wc = new System.Net.WebClient())
                {
                    wc.Credentials = _FtpGetCredentials(userName, password);
                    uri = _FtpGetUri(server, ftpFile);
                    wc.DownloadFile(uri, localFile);
                }
            }
            catch (Exception exc)
            {
                Assistant.ShowError("Při stahování souboru %0 došlo k chybě %1.", uri, exc.Message);
            }
        }
        /// <summary>
        /// Na daný FTP server uloží dodaná data (content) do daného FTP souboru.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public static void FtpUploadContent(string server, string userName, string password, string file, byte[] content)
        {
            string uri = null;
            try
            {
                using (System.Net.WebClient wc = new System.Net.WebClient())
                {
                    wc.Credentials = _FtpGetCredentials(userName, password);
                    uri = _FtpGetUri(server, file);
                    wc.UploadData(uri, content);
                }
            }
            catch (Exception exc)
            {
                Assistant.ShowError("Při uploadu dat %0 došlo k chybě %1.", uri, exc.Message);
            }
        }
        /// <summary>
        /// Na daný FTP server uloží dodaná data (content) do daného FTP souboru.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public static void FtpUploadFile(string server, string userName, string password, string file, string localFile)
        {
            string uri = null;
            try
            {
                using (System.Net.WebClient wc = new System.Net.WebClient())
                {
                    wc.Credentials = _FtpGetCredentials(userName, password);
                    uri = _FtpGetUri(server, file);
                    wc.UploadFile(uri, localFile);
                }
            }
            catch (Exception exc)
            {
                Assistant.ShowError("Při uploadu souboru %0 došlo k chybě %1.", uri, exc.Message);
            }
        }
        /// <summary>
        /// Z daného FTP serveru smaže daný soubor.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public static void FtpDeleteFile(string server, string userName, string password, string file)
        {
            string uri = null;
            try
            {
                uri = _FtpGetUri(server, file);
                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(uri);
                request.Credentials = _FtpGetCredentials(userName, password);
                request.Method = WebRequestMethods.Ftp.DeleteFile;
                request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
                request.Timeout = 3700;
                request.UseBinary = true;
                using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
                {
                    response.Close();
                }
            }
            catch (Exception exc)
            {
                Assistant.ShowError("Při smazání souboru %0 došlo k chybě %1.", uri, exc.Message);
            }
        }
        /// <summary>
        /// Na daném FTP zjistí, zda existuje daný soubor.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public static bool FtpExistsFile(string server, string userName, string password, string file)
        {
            long length = FtpGetLengthOfFile(server, userName, password, file);
            return (length >= 0L);
        }
        /// <summary>
        /// Na daném FTP zjistí, zda existuje daný soubor.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        public static long FtpGetLengthOfFile(string server, string userName, string password, string file)
        {
            long length = -1L;
            string uri = null;
            try
            {
                uri = _FtpGetUri(server, file);
                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(uri);
                request.Credentials = _FtpGetCredentials(userName, password);
                request.Method = WebRequestMethods.Ftp.GetFileSize;
                request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
                request.Timeout = 3700;
                request.UseBinary = true;
                using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
                {
                    length = response.ContentLength;
                    response.Close();
                }
            }
            catch (Exception)
            {   // Pokud soubor nelze otevřít, dojde k chybě, a my vrátíme -1:
                length = -1L;
            }
            return length;
        }
        private static System.Net.ICredentials _FtpGetCredentials(string userName, string password)
        {
            return new System.Net.NetworkCredential(userName, password);
        }
        private static string _FtpGetUri(string server, string file)
        {
            string uri = server;
            if (!uri.StartsWith("ftp://")) uri = "ftp://" + uri;
            if (!uri.EndsWith("/") && !file.StartsWith("/")) uri += "/";
            uri += file;
            return uri;
        }
    }
    #endregion
    #region InitializeMethodAttribute : Atribut, který označuje metody, které se mají vyvolat v rámci inicializace daného objektu.
    /// <summary>
    /// Atribut, který označuje metody, které se mají vyvolat v rámci inicializace daného objektu.
    /// Inicializaci objektu (vyvolání všech odpovídajících metod) zajišťuje metoda InitializeMethodAttribute.CallInitializeMethods(object).
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class InitializeMethodAttribute : Attribute
    {
        /// <summary>
        /// Označí metodu tak, že se bude volat v inicializaci její třídy (pokud konstruktor této třídy zavolá InitializeMethodAttribute.CallInitializeMethods(this);).
        /// Tato varianta neřeší pořadí, metoda má pořadí = 0.
        /// </summary>
        public InitializeMethodAttribute()
        {
            this.Order = 0;
        }
        /// <summary>
        /// Označí metodu tak, že se bude volat v inicializaci její třídy (pokud konstruktor této třídy zavolá InitializeMethodAttribute.CallInitializeMethods(this);).
        /// Zde je možno předat pořadí, metody se volají podle jejich pořadí ve vzestupné řadě.
        /// </summary>
        /// <param name="order"></param>
        public InitializeMethodAttribute(int order)
        {
            this.Order = order;
        }
        /// <summary>
        /// Pořadí volání v rámci inicializace
        /// </summary>
        public int Order { get; private set; }
        /// <summary>
        /// Zajistí vyvolání všech metod, označených atributem [InitializeMethodAttribute] na dodané instanci, v pořadí jejich Order.
        /// Mějme libovolný objekt, v němž má proběhnout jeho inicializace tak, že se postupně volá řada jeho inicializačních metod v jistém pořadí.
        /// Řešení 1: v konstruktoru budeme postupně volat tyto metody. Znamená to, že při přidání nové inicializační metody se tato metoda do konstruktoru.
        /// Řešení 2: patřičné metody (musí být bezparametrické) opatříme atributem [InitializeMethod()], nebo [InitializeMethod(order)], nikam je nezapisujeme. A v konstruktoru jen zavoláme InitializeMethodAttribute.CallInitializeMethods(this);
        /// </summary>
        /// <param name="target"></param>
        public static void CallInitializeMethods(object target)
        {
            if (target == null) return;

            // Získat seznam metod včetně jejich pořadí:
            List<Tuple<int, System.Reflection.MethodInfo>> initMethods = new List<Tuple<int,System.Reflection.MethodInfo>>();
            foreach (var method in target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
            {
                InitializeMethodAttribute ima = method.GetCustomAttributes(true).FirstOrDefault(o => o is InitializeMethodAttribute) as InitializeMethodAttribute;
                if (ima != null)
                    initMethods.Add(new Tuple<int, System.Reflection.MethodInfo>(ima.Order, method));
            }

            // Vyhodnotit seznam, setřídit a postupně zavolat inicializační metody:
            if (initMethods.Count == 0) return;
            if (initMethods.Count > 1)
                initMethods.Sort((a, b) => a.Item1.CompareTo(b.Item1));

            foreach (var init in initMethods)
            {
                var method = init.Item2;
                if (method.GetParameters().Length == 0)
                    method.Invoke(target, new object[0]);
            }
            
        }
    }
    #endregion
}
